package com.zlq.cn.api;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zlq.cn.annotations.ApiParamBean;
import com.zlq.cn.annotations.UnLogin;
import com.zlq.cn.annotations.UnSigned;
import com.zlq.cn.config.Config;
import com.zlq.cn.entity.User;
import com.zlq.cn.service.un.UserService;
import com.zlq.cn.util.des.KFuPayDecryptUtil;
import one.util.streamex.StreamEx;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Set;

/**
 * 拦截controller请求
 * 1.做请求解密操作得到解密后的具体请求参数；UnSigned贴上该注解就不做解密操作
 * 2.校验登录并获得token对应用户信息；UnLogin贴上改注解就不做登录校验
 * 3.解密出具体请求数据并赋值到真实接口参数上
 * 3.1 ApiParamBean贴上该注解会把请求参数转换成对此赋值到该参数
 * 3.2 会把步骤2中获取到的用户信息赋值到接口参数
 * 4.对参数做校验;有该ApiParamBean注解的会启动对象校验，其他的就是参数校验
 * 5.接口返回值统一返回格式{code:200,msg:"请求成功",data:{}}
 * 5.1具体接口返回值统一写Object，返回的结果会被包装成统一格式
 * 5.2如返回ApiResult则直接返回
 * 5.3如返回ApiMessage则会包装统一格式替换msg
 * 5.4统一拦截IllegalArgumentException、ResponseException连接到这两种异常会返回业务错误，其他异常都返回服务器错误
 * 业务判断可以直接抛上述两个异常中断后面的操作
 */
@Aspect
@Component
public class ApiAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass().getName());
    @Resource
    private UserService userService;
    @Autowired
    LocalValidatorFactoryBean validator;

    @Pointcut("execution(public * com.zlq.cn.controller.api.*.*(..))")
    public void declearJoinPointExpression() {
    }

    @Around(value = "declearJoinPointExpression()")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        Object result;
        try {
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            UnLogin unLogin = method.getAnnotation(UnLogin.class);
            UnSigned unSigned = method.getAnnotation(UnSigned.class);
            RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
            if (method.getAnnotation(Bean.class) != null) {
                return joinPoint.proceed();
            }
            //RequestMethod
            if (requestMapping == null ||
                    StreamEx.of(requestMapping.method())
                            .findAny(requestMethod -> requestMethod == RequestMethod.GET)
                            .orElse(null) != null
            ) {
                return joinPoint.proceed();
            }
            // 第0位必须是ApiParam
            Object[] args = joinPoint.getArgs();
            if (args.length > 0) {
                ApiParam apiParam = (ApiParam) args[0];
                JSONObject param = apiParam;
                // 加解密
                if (unSigned == null) {
                    if (StringUtils.isBlank(apiParam.getEncryptData()) || StringUtils.isBlank(apiParam.getRandom())) {
                        return ApiResult.resultError("参数异常");
                    }
                    String decryptData = KFuPayDecryptUtil.decrypt(apiParam.getRandom(), apiParam.getEncryptData(), Config.APP_3DES_KEY);
                    apiParam.setParams(decryptData);
                    // 验证签名
                    if (StringUtils.isBlank(apiParam.getSignData())) {
                        return ApiResult.resultError("异常");
                    }
                    param = JSON.parseObject(decryptData);
                    String signParamStr = KFuPayDecryptUtil.orderedEndKey(param, Config.APP_MD5_KEY);
                    String signData = SecureUtil.md5(signParamStr);
                    // 比对加密参数
                    if (!apiParam.getSignData().equalsIgnoreCase(signData)) {
                        return ApiResult.resultError("异常");
                    }
                }
                //需要登录
                if (unLogin == null) {
                    if (StringUtils.isEmpty(param.getString("token"))) {
                        logger.error("token不能为空;" + methodSignature.getName());
                        return ApiResult.resultError("token不能为空");
                    }
                    User user = userService.findUserByToken(param.getString("token"));
                    if (user == null) {
                        return ApiResult.resultError(ApiCodeEnum.TOKEN_INVALID);
                    }
                    argsConverter(args, param, methodSignature, user);
                } else {
                    argsConverter(args, param, methodSignature, null);
                }
                // 参数校验
                ApiResult apiResult = parameterValid(args, joinPoint);
                if (apiResult != null) {
                    return apiResult;
                }
            }
            result = joinPoint.proceed(args);
        } catch (Throwable e) {
            if (e instanceof ResponseException) {
                return ApiResult.resultError(((ResponseException) e));
            } else if (e instanceof IllegalArgumentException) {
                return ApiResult.resultError(e.getMessage());
            }
            logger.error("错误", e);
            return ApiResult.resultError(ApiCodeEnum.SERVICE_ERROR);
        }
        if (result instanceof ApiResult) {
            return result;
        }
        if (result instanceof ApiMessage) {
            return ApiResult.resultSuccessMessage(((ApiMessage) result).getMessage());
        }
        return ApiResult.resultSuccess(result);
    }

    private void argsConverter(Object[] args, JSONObject apiParam, MethodSignature methodSignature, User user) {
        String[] parameterNames = methodSignature.getParameterNames();
        Method method = methodSignature.getMethod();
        Type[] parameterTypes = method.getGenericParameterTypes();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameterTypes.length; i++) {
            Type parameterType = parameterTypes[i];
            if (parameterType != ApiParam.class) {
                Parameter parameter = parameters[i];
                if (parameter.getAnnotation(ApiParamBean.class) != null) {
                    args[i] = apiParam.toJavaObject(parameterType);
                } else if (parameterType == User.class) {
                    args[i] = user;
                } else {
                    Object value = apiParam.getObject(parameterNames[i], parameterType);
                    if (value != null) {
                        args[i] = value;
                    }
                }
            }
        }
    }

    /**
     * 参数校验
     */
    private ApiResult parameterValid(Object[] args, ProceedingJoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getAnnotation(ApiParamBean.class) != null) {
                Set<ConstraintViolation<Object>> validate = validator.getValidator().validate(args[i]);
                if (!validate.isEmpty()) {
                    for (ConstraintViolation<Object> item : validate) {
                        return ApiResult.resultError(item.getMessage());
                    }
                }
            }
        }
        Set<ConstraintViolation<Object>> constraintViolations = validator.getValidator().forExecutables().validateParameters(joinPoint.getTarget(), method, args);
        if (!constraintViolations.isEmpty()) {
            for (ConstraintViolation<Object> item : constraintViolations) {
                return ApiResult.resultError(item.getMessage());
            }
        }
        return null;
    }
}
